# -*- coding: utf-8 -*-
"""
Import content from zip file.

This import functionality is designed to be useful when site owner want's
to migrate from Plone CMS to Pargo.

Zip file is compatible with export format of `Products.Marshall`_.

Implementation don't want to be flexible but should be simple.
"""
import logging, os.path, re
from datetime import datetime
from lxml import etree
from zipfile import ZipFile, BadZipfile

from zope.interface import implements
from zope.component import adapts
from zope.dublincore.interfaces import IZopeDublinCore

from pargo.interfaces import IContentImporter, IContent
from pargo.content.document import Document
from pargo.content.link import Link
from pargo.content.file import File, Image
from pargo.content.folder import Folder


log = logging.getLogger(__name__)

XMLNS = {
    '': 'http://plone.org/ns/archetypes/',
    'cmf': 'http://cmf.zope.org/namespaces/default/',
    'dc': 'http://purl.org/dc/elements/1.1/',
    'xmp': 'adobe:ns:meta',
    }

FILE_FIELD_TAG = re.compile(r'<field name="file">.*?</field>', re.DOTALL)


class ZipImportFile(ZipFile):
    """
    ZipFile with few useful addons.
    """
    _v_cache = {}
    
    def listdir(self, path):
        """
        Return a list containing the names of the files in the directory given by path.
        """
        if path == '/':
            result = [fn for fn in self.namelist() if fn.count('/') == 0]
        else:
            if path.startswith('/'):
                path = path[1:]
            if not path.endswith('/'):
                path = path + '/'
            
            level = path.count('/')
            result = [fn for fn in self.namelist() if fn.startswith(path) and fn.count('/')==level]
        
        result = [fn for fn in result if '.metadata' not in fn]        
        return result
    
    def get_metadata(self, path):
        """
        Return metadata of object given by path.
        """
        if path in self._v_cache:
            return self._v_cache[path]
        
        dirname = os.path.dirname(path)
        filename = os.path.basename(path)
        
        meta_path = '/'.join((dirname, '.metadata', filename))
        if meta_path.startswith('/'):
            meta_path = meta_path[1:]
        
        data = self.read(meta_path)
        
        ## remove file content from object's metadata, because file content
        ## is not utf-8 (and lxml gives XMLSyntaxError)  
        data = FILE_FIELD_TAG.sub('', data)
        
        meta = etree.fromstring(data)
        
        def find_text(key):
            if key.count(':') == 1:
                xmlns, realkey = key.split(':')
            else:
                xmlns, realkey = '', key
            key = '{%s}%s' % (XMLNS[xmlns], realkey)
            text = meta.findtext(key)
            if text is None:
                return u''
            if isinstance(text, str):
                return unicode(text, 'utf-8').strip()
            return text.strip()
        
        def find_date(key):
            text = find_text(key)
            return datetime.strptime(text, '%Y-%m-%dT%H:%M:%SZ')
        
        result = {
            'type': str(find_text('cmf:type')),
            'uid': str(find_text('uid')),
            'id': str(find_text("field[@name='id']")),
            'dublincore': {
                'title': find_text('dc:title'),
                'description': find_text('dc:description'),
                'creator': find_text('dc:creator'),
                'language': find_text('dc:language'),
                'created': find_date('xmp:CreateDate'),
                'modified': find_date('xmp:ModifyDate'),
                },
            }
        self._v_cache[path] = result
        return result


class ContentImporter(object):
    """
    Import content from zip file.
    """
    implements(IContentImporter)
    adapts(IContent)
    
    _zf = None
    
    _known_types = {
        'Document': (Document, None),
        'Link': (Link, 'url'),
        'File': (File, 'file'),
        'Image': (Image, 'file'),
        'Folder': (Folder, None),
        }
    
    def __init__(self, context):
        self.context = context
        
    def do_import(self, filename):
        """
        Import content from given file.
        
        :Parameters:
            filename : string
                path to file with import data
        
        :Return: (bool)
            True if import was successfull 
        """
        if not os.path.isfile(filename):
            return False
        
        try:
            self._zf = ZipImportFile(filename)
        except BadZipfile:
            return False
        
        for obj_path in self._zf.listdir('/'):
            handler = self._find_handler(obj_path)            
            handler(obj_path, self.context)
        
        return True

    def _find_handler(self, path):
        """
        Find 
        """
        obj_type = self._zf.get_metadata(path)['type']
        
        handler_name = '_import_' + obj_type.replace(' ', '')
        handler = getattr(self, handler_name, None)
        if not handler:
            handler = self._import_unknown
        
        return handler

    def _import_unknown(self, path, context):
        """
        Import not supported object.
        """
        meta = self._zf.get_metadata(path)
        log.debug('Not supported object type: %s' % meta['type'])
    
    def _import_standard(self, path, context):
        """
        Import standard object.
        """
        meta = self._zf.get_metadata(path)
        factory, primary_field = self._known_types[meta['type']]
        
        context[meta['id']] = factory()
        new_obj = context[meta['id']]
        
        new_obj.uid = meta['uid']
        dc = IZopeDublinCore(new_obj)
        for key, value in meta['dublincore'].items():
            if value:
                setattr(dc, key, value)
        
        if primary_field:
            setattr(new_obj, primary_field, self._zf.read(path))
        
        log.debug('New %s created (path: %s, uid: %s)' % (meta['type'], path, meta['uid']))
        return new_obj       
        
    _import_Link = _import_standard
    _import_File = _import_standard
    _import_Image = _import_standard
    
    def _import_Document(self, path, context):
        """
        Import Document.
        """
        new_obj = self._import_standard(path, context)
        text = unicode(self._zf.read(path), 'utf-8')
        new_obj.text = text        
        
    def _import_Folder(self, path, context):
        """
        Import Folder.
        """
        new_obj = self._import_standard(path, context)
        
        for obj_path in self._zf.listdir(path):
            handler = self._find_handler(obj_path)            
            handler(obj_path, new_obj)
        
        return new_obj
